﻿using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Discovery;
using EasyComm.Contracts;
using EasyComm.Discovery;
using EasyComm.Hosting.Configuration;
using EasyComm.ServiceUnit;
using EasyComm.Utilities;

namespace EasyComm.Hosting.Extension
{
    internal class SLBDiscoveryExtension : IExtension<ServiceHostBase>, IDisposable
    {
        private SLBProxyServiceHost _owner;
        private Uri _discoveryProxyAddress;
        private Type _workerAdminContract;

        public SLBDiscoveryExtension(Type workerAdminContract, Uri discoveryProxyAddress)
        {
            _discoveryProxyAddress = discoveryProxyAddress;
            _workerAdminContract = workerAdminContract;
        }

        void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner)
        {
            _owner = owner as SLBProxyServiceHost;
            PopulateFromManagedDiscovery();
            ListenToAnnouncements();
        }

        void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner)
        {
            this.Dispose();
        }

        public void Dispose()
        {
        }

        private void PopulateFromManagedDiscovery()
        {
            if (!NetUtils.TCPOnlineCheck(_discoveryProxyAddress.Host, _discoveryProxyAddress.Port))
            {
                Trace("Proxy server is offline ('{0}'). Bypassing managed discovery search.", _discoveryProxyAddress);
                return;
            }

            var binding = GenericConfiguration.GetAnnouncementBinding();

            DiscoveryEndpoint discoveryEndpoint = new DiscoveryEndpoint(binding, new EndpointAddress(_discoveryProxyAddress));
            DiscoveryClient discoveryClient = new DiscoveryClient(discoveryEndpoint);
            var results = discoveryClient.Find(new ServiceDiscoveryCriteria(new Type[] { _workerAdminContract }));
            //var results = discoveryClient.Find(new FindCriteria(typeof(IAdminSLBContract)));

            foreach (var endpoint in results.Endpoints)
            {
                AddEndpointToRoutingTable(endpoint);
            }
            discoveryClient.Close();
        }

        /// <summary>
        /// Update the routing table based on UDB announcement
        /// </summary>
        private void ListenToAnnouncements()
        {
            AnnouncementService announcementService = new AnnouncementService();
            announcementService.OnlineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(ServiceOnlineEvent);
            announcementService.OfflineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(ServiceOffLineEvent);
            ServiceHost announcementServiceHost = new ServiceHost(announcementService);
            try
            {
                announcementServiceHost.AddServiceEndpoint(new UdpAnnouncementEndpoint());
                announcementServiceHost.Open();
            }
            catch (CommunicationException communicationException)
            {
                throw new FaultException("Can't listen to notification of services " + communicationException.Message);
            }
            catch (TimeoutException timeoutException)
            {
                throw new FaultException("Timeout trying to open the notification service " + timeoutException.Message);
            }
        }

        /// <summary>
        /// Fires when a service is online
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ServiceOffLineEvent(object sender, AnnouncementEventArgs e)
        {
            Trace("Endpint offline detected: {0}", e.EndpointDiscoveryMetadata.Address);
            RemoveEndpointFromRoutingTable(e.EndpointDiscoveryMetadata);
        }

        /// <summary>
        /// Fires when a service goes offline
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ServiceOnlineEvent(object sender, AnnouncementEventArgs e)
        {
            Trace("Endpoint online detected: {0}", e.EndpointDiscoveryMetadata.Address);
            AddEndpointToRoutingTable(e.EndpointDiscoveryMetadata);
        }

        private void AddEndpointToRoutingTable(EndpointDiscoveryMetadata endpointMetadata)
        {
            if (endpointMetadata.ContractTypeNames.Count(item => item.Name.Contains("IMetadataExchange")) > 0)
            {
                Trace("Bypassing metadata online announcement url: '{0}'.", endpointMetadata.Address.Uri);
                return;
            }

            Binding binding = GenericConfiguration.GetMainBinding();
            EndpointAddress address = endpointMetadata.Address;

            ContractDescription workerAdminContractDescription = ContractDescription.GetContract(_workerAdminContract);
            ServiceEndpoint endpoint = new ServiceEndpoint(workerAdminContractDescription, binding, address);

            bool workerAdminAdded = false;
            if (endpointMetadata.ContractTypeNames.Count(XmlName => _workerAdminContract.Name == XmlName.Name) > 0)
            {
                //worker admin endpoint
                AdminServiceUnit<IAdminWorkerContract> workerAdminUnit = new AdminServiceUnit<IAdminWorkerContract>(_owner) { Type = AdminServiceType.WorkerAdmin, Status = ServiceStatus.Online, Host = address.Uri.DnsSafeHost, Port = address.Uri.Port };
                this._owner.Owner._workerAdminServiceList.Add(workerAdminUnit);
                Trace("Worker admin endpoint of type added: {0}", endpointMetadata.Address);
                workerAdminAdded = true;
            }

            if (!workerAdminAdded)
            {
                Trace("Service does not implement any of the registered contracts. Bypassing...");
                return;
            }
        }

        private void RemoveEndpointFromRoutingTable(EndpointDiscoveryMetadata endpointMetadata)
        {
            if (endpointMetadata.ContractTypeNames.Count(item => item.Name.Contains("IMetadataExchange")) > 0)
            {
                Trace("Bypassing metadata offline announcement url: '{0}'.", endpointMetadata.Address.Uri);
                return;
            }

            AdminServiceUnit<IAdminWorkerContract> workerAdminUnit = this._owner.Owner._workerAdminServiceList[endpointMetadata.Address.Uri];
            if (workerAdminUnit != null)
            {
                this._owner.Owner._workerAdminServiceList.Remove(workerAdminUnit);
                Trace("Worker Admin Endpoint removed: {0}", endpointMetadata.Address);
            }
        }

        private void Trace(string msg, params object[] args)
        {
            System.Diagnostics.Trace.WriteLine(string.Format(msg, args));
        }
    }
}


